Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementation of durable file rename #60

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

clkamp
Copy link

@clkamp clkamp commented May 2, 2020

For some use cases, one does not only need atomic and or durable writing of files, but also durable rename of files. A standard use case for this is delivery of mails to a Maildir (especially on a unix that is not linux, so that anonymous temp files are not available), where a new mail is first delivered to a temporary directory and then renamed to its final location.

This pull request implements a durable renaming of files.

@clkamp clkamp force-pushed the renameFileDurable branch from d9f367f to 09e05f1 Compare May 2, 2020 09:23
-- === Cross-Platform support
--
-- This function is a noop on Windows platforms.
--
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should include a @since, as well as a changelog entry and minor version bump. Also: @nh2 or @lehins, could one of you take a look?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the feedback, added those.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could one of you take a look?

Will do!

@clkamp clkamp force-pushed the renameFileDurable branch from 09e05f1 to e78e8b1 Compare May 3, 2020 12:01
--
-- === Cross-Platform support
--
-- This function is a noop on Windows platforms.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this text needs to be adjusted: On ensureFileDurable it's right because there it does nothing, but for renameFileDurable it certainly still does the rename, it just doens't make it durable.

-- is because on an async filesystem the write of the old directory might
-- already written to disk and the change on the new directory is not. It the
-- function call returns, the change is durable. Nevertheless, this will not
-- happen on filesystems using journaling, that is, allmost all modern filesystems.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it not a bit confusing to say "This is also atomic"? Because it should be only atomic on the same filesystem -- rename() does not work at all across different file systems (on Linux), where we get from System.Directory.renameFile:

Prelude System.Directory> renameFile "testfile" "mnt/testfile"
*** Exception: testfile: renameFile:renamePath:rename: unsupported operation (Invalid cross-device link)

@@ -1,5 +1,5 @@
name: unliftio
version: 0.2.12.1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@snoyberg FYI Looks like the 0.2.13 went missing.

withDirectory (takeDirectory newName) $ \newDirFd -> do
renameFile oldName newName
fsyncDirectoryFd "renameFileDurable" newDirFd
fsyncDirectoryFd "renameFileDurable" oldDirFd
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possible optimisation:

For the common case that oldName and newName are in the same directory, do only 1 withDirectory and do only 1 fsync().

withDirectory (takeDirectory newName) $ \newDirFd -> do
renameFile oldName newName
fsyncDirectoryFd "renameFileDurable" newDirFd
fsyncDirectoryFd "renameFileDurable" oldDirFd
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another possible improvement could be to use renameat(), with is directory-FD based renaming.

That would be even better than what we have here, because then the fsync() would operate on the same FD as the rename does, giving even better guarantees (see also https://stackoverflow.com/questions/37288453/calling-fsync2-after-close2/50158433#50158433 for a related topic on files).

That is not necessary for this PR though, having a renameFileDurable implemented this way is already much better than not having it.

Copy link
Contributor

@lehins lehins left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am sceptical that a simple renameFile will be enough for durability.

In order for the fsync to work in order to guarantee durability on the actual file change we need to open that file with openat in the directory and then do the atomic renameat followed by an fsync on the file AND the containing directory Fd. In the case of rename we don't need to open the file, but renameat might still be necessary. I didn't find anything online about it, so I might be wrong and a simple renameFile followed by fsync on directories might be sufficient.

Another side note is that renameFile only works on files and because of that performs extra checks that the file path is not a directory, which are themselves not atomic.

Just to be safe what I recommend is adding an actual renameFilePathAtomicDurable (renames are guaranteed to be atomic, since they aren't possible across devices, as @nh2 noted in his comment: https://github.com/fpco/unliftio/pull/60/files#r419109086) That function will work on both files and directories alike. I started on this approach here: lehins@d98f605
If @nh2, @snoyberg and @clkamp you guys agree that this is the right approach @clkamp feel free to pick up from that commit and merge it into you work here.

Here is a pretty useful SO question on the topic: https://stackoverflow.com/questions/3764822/how-to-durably-rename-a-file-in-posix

I also agree with all of the comments @nh2 made and want to add:

  • I'd appreciate if formatting and indentation was kept consistent with the rest of the code
  • At least a couple of tests added to the test suite

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants